using System;
using SovaDataService.SoDataServices;
using SovaDataService.SoModels;
using SovaDataService.SovaDataServices;
using SovaDataService.SovaModels;
using System.Linq;
using Xunit;

namespace SovaDataService.Tests
{
    public class DataServiceTests
    {
        private string _testAccountName = "Test Testerstein";
        private string _testAccountEmail = "test@email.com";
        private string _testAccountPassword = "password";

        /* Tests if objects has correct properties */

        [Fact]
        public void AuthorObjectHasProperties()
        {
            // Given.
            var author = new Author();
            // Then.
            Assert.Equal(0, author.Id);
            Assert.Null(author.Name);
            Assert.Equal(0, author.CreationDate);
            Assert.Null(author.Age);
            Assert.Null(author.Location);
        }
        
        [Fact]
        public void QuestionObjectHasProperties()
        {
            // Given.
            var question = new Question();
            // Then.
            Assert.Equal(0, question.Id);
            Assert.Null(question.Title);
            Assert.Null(question.Body);
            Assert.Equal(0, question.Score);
            Assert.Equal(0, question.CreationDate);
            Assert.Null(question.ClosedDate);
            Assert.Null(question.Author);
            Assert.Null(question.AcceptedAnswerId);
        }

        [Fact]
        public void AnswerObjectHasProperties()
        {
            // Given.
            var answer = new Answer();
            // Then.
            Assert.Equal(0, answer.Id);
            Assert.Null(answer.Body);
            Assert.Equal(0, answer.Score);
            Assert.Equal(0, answer.CreationDate);
            Assert.Null(answer.Question);
            Assert.Null(answer.Author);
        }

        [Fact]
        public void TagObjectHasProperties()
        {
            // Given.
            var tag = new Tag();
            // Then.
            Assert.Equal(0, tag.Id);
            Assert.Null(tag.Name);
        }

        [Fact]
        public void CommentObjectHasProperties()
        {
            // Given.
            var comment = new Comment();
            // Then.
            Assert.Equal(0, comment.Id);
            Assert.Null(comment.Text);
            Assert.Equal(0, comment.Score);
            Assert.Equal(0, comment.CreationDate);
            Assert.Null(comment.Author);
            Assert.Null(comment.Question);
            Assert.Null(comment.Answer);
        }

        [Fact]
        public void AccountObjectHasProperties()
        {
            // Given.
            var account = new Account();
            // Then.
            Assert.Equal(0, account.Id);
            Assert.Null(account.Name);
            Assert.Null(account.Email);
            Assert.Null(account.Password);
        }

        [Fact]
        public void MarkedQuestionHasProperties()
        {
            // Given.
            var markedQuestion = new MarkedQuestion();
            // Then.
            Assert.Equal(0, markedQuestion.AccountId);
            Assert.Equal(0, markedQuestion.QuestionId);
            Assert.Null(markedQuestion.Account);
            Assert.Null(markedQuestion.Question);
            Assert.Null(markedQuestion.Annotation);
        }

        [Fact]
        public void SearchObjectHasProperties()
        {
            // Given.
            var search = new Search();
            // Then.
            Assert.Equal(0, search.Id);
            Assert.Equal(0, search.AccountId);
            Assert.Null(search.Account);
            Assert.Null(search.SearchText);
            Assert.Equal(0, search.SearchDate);
        }

        /* Tests if methods returns correct data from the database. */
        
        // Question model tests.
        
        [Fact]
        public void GetQuestions_NoArgument_ReturnsAllQuestions()
        {
            // Given.
            var service = new QuestionDataService();
            // When.
            var questions = service.GetAll();
            // Then.
            Assert.Equal(2198, questions.Count());
            Assert.Equal(19, questions.First().Id);
            Assert.Equal("What is the fastest way to get the value of π?", questions.First().Title);
            Assert.Contains("pitimes.c", questions.First().Body);
            Assert.Equal(3712, questions.First().Body.Length);
            Assert.Equal(164, questions.First().Score);
            Assert.Equal(1217560882, questions.First().CreationDate);
            Assert.Null(questions.First().ClosedDate);
            Assert.Equal("Chris Jester-Young", questions.First().Author.Name);
            Assert.Equal(531, questions.First().AcceptedAnswerId);
        }
        
        // TODO: Fix this because it fails with actual "7310559" instead of "636712"
        [Fact]
        public void GetQuestionsBySearch_ValidArgumentsProvided_ReturnsListOfQuestions()
        {
            // Given.
            var questionsDataService = new QuestionDataService();
            var accountService = new AccountDataService();
            var testAccount = new Account()
            {
                Name = _testAccountName,
                Email = _testAccountEmail,
                Password = _testAccountPassword
            };
            var createdAccount = accountService.Create(testAccount);
            // When.
            var questions = questionsDataService.GetQuestionsBySearch("orange", createdAccount.Id, out int count);
            // Then.
            Assert.Equal(636712, questions.First().Id);
            Assert.Equal(16593562, questions.Last().Id);
            // Cleanup.
            Assert.True(accountService.Delete(testAccount.Id));
        }
        
        [Fact]
        public void GetQuestion_IdProvided_ReturnsQuestion()
        {
            // Given.
            var service = new QuestionDataService();
            // When.
            var question = service.GetOne(1711);
            // Then.
            Assert.Equal("What is the single most influential book every programmer should read?", question.Title);
        }
        
        
        [Fact]
        public void GetQuestion_InvalidIdProvided_ReturnsNull()
        {
            // Given.
            var service = new QuestionDataService();
            // When.
            var question = service.GetOne(1919);
            // Then.
            Assert.Null(question);
        }
        
        /* Author model tests. */

        [Fact]
        public void GetAuthors_NoArgument_ReturnsAllAuthors()
        {
            // Given.
            var service = new AuthorDataService();
            // When.
            var authors = service.GetAll();
            // Then.
            Assert.Equal(16593, authors.Count());
            Assert.Equal(1, authors.First().Id);
            Assert.Equal("Jeff Atwood", authors.First().Name);
            Assert.Equal(1217506951, authors.First().CreationDate);
            Assert.Equal(45, authors.First().Age);
            Assert.Equal("El Cerrito, CA", authors.First().Location.Place);
        }
        
        [Fact]
        public void GetAuthor_ValidIdProvided_ReturnsAuthor()
        {
            // Given.
            var service = new AuthorDataService();
            // When.
            var author = service.GetOne(196);
            // Then.
            Assert.Equal("Spencer", author.Name);
        }
        
        [Fact]
        public void GetAuthor_InvalidIdProvided_ReturnsNull()
        {
            // Given.
            var service = new AuthorDataService();
            // When.
            var author = service.GetOne(6); 
            // Then.
            Assert.Null(author);        
        }
        
        /* Tags model tests. */

        // TODO: Fix this because it fails with actual value being "node.js" instead of "cakephp".
        [Fact]
        public void GetTags_NoArgument_ReturnsAllTags()
        {
            // Given.
            var service = new TagDataService();
            // When.
            var tags = service.GetAll();
            // Then.
            Assert.Equal(1, tags.First().Id);
            Assert.Equal("cakephp", tags.First().Name);
        }

        // TODO: fix this because it fails with actual value: "unit-testing" instead of "c#".
        [Fact]
        public void GetTags_ValidIdProvided_RetrunsOneTag()
        {
            // Given.
            var service = new TagDataService();
            // When.
            var tag = service.GetOne(32);
            // Then.
            Assert.Equal("c#", tag.Name);
        }

        [Fact]
        public void GetTags_InvalidIdProvided_ReturnsNull()
        {
            // Given.
            var service = new TagDataService();
            // When.
            var tag = service.GetOne(1919);
            // Then.
            Assert.Null(tag);
        }

        /* Answer model tests */
        
        [Fact]
        public void GetAnswers_NoArgument_ReturnsAllAnswers()
        {
            // Given.
            var service = new AnswerDataService();
            // When.
            var answers = service.GetAll();
            // Then.
            Assert.Equal(11182, answers.Count());
            Assert.Equal(71, answers.First().Id);
            Assert.Equal("<p>Here's a general description of a technique for calculating pi that I learnt in high school.</p>&#xA;&#xA;<p>I only share this because I think it is simple enough that anyone can remember it, indef", answers.First().Body.Substring(0, 200));
            Assert.Equal(1446, answers.First().Body.Length);
            Assert.Equal(43, answers.First().Score);
            Assert.Equal(1217590680, answers.First().CreationDate);
            Assert.Equal("Leon Bambrick", answers.First().Author.Name);
        }
        
        [Fact]
        public void GetAnswer_IdProvided_ReturnsAnswer()
        {
            // Given.
            var service = new AnswerDataService();
            // When.
            var answer = service.GetOne(1222377);
            // Then.
            Assert.Equal("<p>Try this:</p>&#xA;&#xA;<pre><code>&amp;quot;&#xA;</code></pre>&#xA;", answer.Body);
        }
        
        
        [Fact]
        public void GetAnswer_InvalidIdProvided_ReturnsNull()
        {
            // Given.
            var service = new AnswerDataService();
            // When.
            var answer = service.GetOne(6184);
            // Then.
            Assert.Null(answer);
        }

        [Fact]
        public void GetAnswersForQuestion_CorrectQuestionIdProvided_ReturnsListOfAnswers()
        {
            // Given.
            var service = new AnswerDataService();
            // When.
            var answers = service.GetAnswersForQuestion(1669);
            // Then.
            Assert.Equal(34, answers.Count());
            Assert.Equal(1672, answers.First().Id);
        }

        [Fact]
        public void GetAnswersForQuestion_InvalidQuestionIdProvided_ReturnsEmptyListOfAnswers()
        {
            // Given.
            var service = new AnswerDataService();
            // When.
            var answers = service.GetAnswersForQuestion(3650125);
            // Then.
            Assert.Empty(answers);
        }
        
        /* Comment model tests. */
        
        [Fact]
        public void GetComments_NoArgument_ReturnsAllComments()
        {
            // Given.
            var service = new CommentDataService();
            // When.
            var comments = service.GetAll();
            // Then.
            Assert.Equal(31426, comments.Count());
            Assert.Equal(120, comments.First().Id);
            Assert.Equal("I'd almost hope people not know about this feature. Far preferable that they organise their code into smaller, more meaningful methods and simply use `return`, in most cases. There is a great furore at the moment around this language construct being added to PHP6.", comments.First().Text);
            Assert.Equal(264, comments.First().Text.Length);
            Assert.Equal(1, comments.First().Score);
            Assert.Equal(1220794126, comments.First().CreationDate);
            Assert.Equal("Cheekysoft", comments.First().Author.Name);
        }
        
        [Fact]
        public void GetComment_IdProvided_ReturnsComment()
        {
            // Given.
            var service = new CommentDataService();
            // When.
            var comment = service.GetOne(910);
            // Then.
            Assert.Equal("ha ha. very smart :)", comment.Text);
        }
        
        [Fact]
        public void GetComment_InvalidIdProvided_ReturnsNull()
        {
            // Given.
            var service = new CommentDataService();
            // When.
            var answer = service.GetOne(121);
            // Then.
            Assert.Null(answer);
        }

        [Fact]
        public void GetCommentsForQuestion_CorrectQuestionIdProvided_ReturnsListOfComments()
        {
            // Given.
            var service = new CommentDataService();
            // When.
            var comments = service.GetCommentsForQuestion(52002);
            // Then.
            Assert.Equal(9, comments.Count());
            // TODO: fix this. Im pretty sure there is an error in this assert. No "score"-value in q, an or c matces 902 in the database
            // Comment id (sorted by asc) matches 902 however. Changed to assert "Id"
            Assert.Equal(902, comments.First().Id);
        }

        [Fact]
        public void GetCommentsForQuestion_InvalidQuestionIdProvided_ReturnsEmptyListOfComments()
        {
            // Given.
            var service = new CommentDataService();
            // When.
            var comments = service.GetCommentsForQuestion(255612);
            // Then.
            Assert.Empty(comments);
        }

        [Fact]
        public void GetCommentsForAnswer_CorrectAnswerIdProvided_ReturnsListOfComments()
        {
            // Given.
            var service = new CommentDataService();
            // When.
            var comments = service.GetCommentsForAnswer(42535);
            // Then.
            Assert.Equal(3, comments.Count());
            Assert.Equal("Sure, but what about a solution using O(1) memory?", comments.First().Text);
        }

        [Fact]
        public void GetCommentsForAnswer_InvalidAnswerIdProvided_ReturnsEmptyListOfComments()
        {
            // Given.
            var service = new CommentDataService();
            // When.
            var comments = service.GetCommentsForAnswer(713);
            // Then.
            Assert.Empty(comments);
        }

        /* Account model tests */
        [Fact]
        public void CreateAccount_ValidDataProvided_AccountisCreated()
        {
            // Given.
            var service = new AccountDataService();
            Account account = new Account()
            {
                Name = _testAccountName,
                Email = _testAccountEmail,
                Password = _testAccountPassword
            };
            // When.
            var createdAccount = service.Create(account);
            // Then.
            Assert.Equal(_testAccountName, createdAccount.Name);
            Assert.Equal(_testAccountEmail, createdAccount.Email);
            Assert.Equal(_testAccountPassword, createdAccount.Password);
            // Cleanup.
            service.Delete(createdAccount.Id);
        }

        [Fact]
        public void DeleteAccount_ValidAccountIdProvided_AccountIsDeleted()
        {
            // Given.
            var service = new AccountDataService();
            Account testAccount = new Account()
            {
                Name = _testAccountName,
                Email = _testAccountEmail,
                Password = _testAccountPassword
            };
            var createdAccount = service.Create(testAccount);
            var oldAccount = service.GetOne(createdAccount.Id);        
            // Then.
            Assert.True(service.Delete(oldAccount.Id));
            Assert.Null(service.GetOne(createdAccount.Id));
        }

        /* MarkedQuestion model tests */
        [Fact]
        public void CreateDeleteMarkedQuestion_ValidDataProvided_SuccessfullyCreated()
        {
            // Given.
            var service = new MarkedQuestionDataService();
            var accountService = new AccountDataService();
            var testAccount = new Account()
            {
                Name = _testAccountName,
                Email = _testAccountEmail,
                Password = _testAccountPassword
            };
            var createdAccount = accountService.Create(testAccount);
            var markedQuestion = new MarkedQuestion()
            {
                Annotation = "test",
                QuestionId = 13724063,
                AccountId = createdAccount.Id
            };
            // When.
            var createdMarkedQuestion = service.Create(markedQuestion);
            // Then.
            Assert.Equal(createdAccount.Id, createdMarkedQuestion.AccountId);
            Assert.Equal("test", createdMarkedQuestion.Annotation);
            // Cleanup.
            Assert.True(service.Delete(createdMarkedQuestion.QuestionId, createdMarkedQuestion.AccountId));
            Assert.True(accountService.Delete(testAccount.Id));
        }

        [Fact]
        public void GetFirstMarkedQuestion_ReturnsAccountName()
        {
            // Given.
            var service = new MarkedQuestionDataService();
            var accountService = new AccountDataService();
            var testAccount = new Account()
            {
                Name = _testAccountName,
                Email = _testAccountEmail,
                Password = _testAccountPassword
            };
            var createdAccount = accountService.Create(testAccount);
            var newMarkedQuestion = new MarkedQuestion()
            {
                Annotation = "test",
                QuestionId = 13724063,
                AccountId = createdAccount.Id
            };
            var createdMarkedQuestion = service.Create(newMarkedQuestion);
            // When.
            var markedQuestion = service.GetOne(newMarkedQuestion.QuestionId, newMarkedQuestion.AccountId);
            // Then.
            Assert.Equal("test", markedQuestion.Annotation);
            Assert.Equal("Test Testerstein", markedQuestion.Account.Name);
            Assert.Equal("if/else constructs inside and outside functions", markedQuestion.Question.Title);
            // Cleanup.
            Assert.True(service.Delete(markedQuestion.QuestionId, markedQuestion.AccountId));
            Assert.True(accountService.Delete(createdAccount.Id));
        }

        /* Search model tests. */
        
        // TODO: fix it. Fails with Npgsql.PostgresException : 23503: insert or update on table "search" violates foreign key constraint "search_account_account_id_fk"
        [Fact]
        public void Searching_SearchTextAndCorrectAccountIdProvided_ReturnsSearchHistory()
        {
            // Given.
            var searchDataService = new SearchDataService();
            var questionDataService = new QuestionDataService();
            var accountId = 13;
            var searchText = "orange";
            // When.
            var questions = questionDataService.GetQuestionsBySearch(searchText, accountId, out int count);
            var search = searchDataService.GetAll();
            // Then.
            Assert.Equal(accountId, search.Last().AccountId);
            Assert.Equal(searchText, search.Last().SearchText);
        }
    }
}